Extend Compile API to handle flags like --tests and --bins
authorBen Wiederhake <BenWiederhake.GitHub@gmx.de>
Thu, 23 Mar 2017 22:45:23 +0000 (23:45 +0100)
committerBen Wiederhake <BenWiederhake.GitHub@gmx.de>
Mon, 10 Apr 2017 18:20:32 +0000 (20:20 +0200)
src/cargo/core/manifest.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_install.rs

index 81c8e9f6b25aed23e3e344d30d8f27cdb4890722..8f826d303dc7ed932c727340a441af1298f1a507 100644 (file)
@@ -453,6 +453,14 @@ impl Target {
         }
     }
 
+    pub fn is_bin_example(&self) -> bool {
+        // Needed for --all-examples in contexts where only runnable examples make sense
+        match self.kind {
+            TargetKind::ExampleBin => true,
+            _ => false
+        }
+    }
+
     pub fn is_test(&self) -> bool { self.kind == TargetKind::Test }
     pub fn is_bench(&self) -> bool { self.kind == TargetKind::Bench }
     pub fn is_custom_build(&self) -> bool { self.kind == TargetKind::CustomBuild }
index 39718e6fa0135d5e06fd46d7fb75bb8d0e079a7f..ca68bfe1f994184b7c5f9d94a01189d17c470fcb 100644 (file)
@@ -78,7 +78,7 @@ impl<'a> CompileOptions<'a> {
             spec: ops::Packages::Packages(&[]),
             mode: mode,
             release: false,
-            filter: ops::CompileFilter::new(false, &[], &[], &[], &[]),
+            filter: CompileFilter::Everything { required_features_filterable: false },
             message_format: MessageFormat::Human,
             target_rustdoc_args: None,
             target_rustc_args: None,
@@ -125,6 +125,12 @@ impl<'a> Packages<'a> {
     }
 }
 
+#[derive(Clone, Copy)]
+pub enum FilterRule<'a> {
+    All,
+    Just (&'a [String]),
+}
+
 pub enum CompileFilter<'a> {
     Everything {
         /// Flag whether targets can be safely skipped when required-features are not satisfied.
@@ -132,10 +138,10 @@ pub enum CompileFilter<'a> {
     },
     Only {
         lib: bool,
-        bins: &'a [String],
-        examples: &'a [String],
-        tests: &'a [String],
-        benches: &'a [String],
+        bins: FilterRule<'a>,
+        examples: FilterRule<'a>,
+        tests: FilterRule<'a>,
+        benches: FilterRule<'a>,
     }
 }
 
@@ -301,17 +307,56 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
     }
 }
 
+impl<'a> FilterRule<'a> {
+    pub fn new(targets: &'a [String], all: bool) -> FilterRule<'a> {
+        if all {
+            FilterRule::All
+        } else {
+            FilterRule::Just(targets)
+        }
+    }
+
+    fn matches(&self, target: &Target) -> bool {
+        match *self {
+            FilterRule::All => true,
+            FilterRule::Just(targets) => {
+                targets.iter().any(|x| *x == target.name())
+            },
+        }
+    }
+
+    fn is_specific(&self) -> bool {
+        match *self {
+            FilterRule::All => true,
+            FilterRule::Just(targets) => !targets.is_empty(),
+        }
+    }
+
+    pub fn try_collect(&self) -> Option<Vec<String>> {
+        match *self {
+            FilterRule::All => None,
+            FilterRule::Just(targets) => Some(targets.iter().map(|t| t.clone()).collect()),
+        }
+    }
+}
+
 impl<'a> CompileFilter<'a> {
     pub fn new(lib_only: bool,
-               bins: &'a [String],
-               tests: &'a [String],
-               examples: &'a [String],
-               benches: &'a [String]) -> CompileFilter<'a> {
-        if lib_only || !bins.is_empty() || !tests.is_empty() ||
-           !examples.is_empty() || !benches.is_empty() {
+               bins: &'a [String], all_bins: bool,
+               tsts: &'a [String], all_tsts: bool,
+               exms: &'a [String], all_exms: bool,
+               bens: &'a [String], all_bens: bool) -> CompileFilter<'a> {
+        let rule_bins = FilterRule::new(bins, all_bins);
+        let rule_tsts = FilterRule::new(tsts, all_tsts);
+        let rule_exms = FilterRule::new(exms, all_exms);
+        let rule_bens = FilterRule::new(bens, all_bens);
+
+        if lib_only || rule_bins.is_specific() || rule_tsts.is_specific()
+                    || rule_exms.is_specific() || rule_bens.is_specific() {
             CompileFilter::Only {
-                lib: lib_only, bins: bins, examples: examples, benches: benches,
-                tests: tests,
+                lib: lib_only, bins: rule_bins,
+                examples: rule_exms, benches: rule_bens,
+                tests: rule_tsts,
             }
         } else {
             CompileFilter::Everything {
@@ -324,7 +369,7 @@ impl<'a> CompileFilter<'a> {
         match *self {
             CompileFilter::Everything { .. } => true,
             CompileFilter::Only { lib, bins, examples, tests, benches } => {
-                let list = match *target.kind() {
+                let rule = match *target.kind() {
                     TargetKind::Bin => bins,
                     TargetKind::Test => tests,
                     TargetKind::Bench => benches,
@@ -333,12 +378,167 @@ impl<'a> CompileFilter<'a> {
                     TargetKind::Lib(..) => return lib,
                     TargetKind::CustomBuild => return false,
                 };
-                list.iter().any(|x| *x == target.name())
+                rule.matches(target)
             }
         }
     }
 }
 
+#[derive(Clone, Copy, Debug)]
+struct BuildProposal<'a> {
+    target: &'a Target,
+    profile: &'a Profile,
+    required: bool,
+}
+
+fn generate_auto_targets<'a>(mode: CompileMode, targets: &'a [Target],
+                             profile: &'a Profile,
+                             dep: &'a Profile,
+                             required_features_filterable: bool) -> Vec<BuildProposal<'a>> {
+    match mode {
+        CompileMode::Bench => {
+            targets.iter().filter(|t| t.benched()).map(|t| {
+                BuildProposal {
+                    target: t,
+                    profile: profile,
+                    required: !required_features_filterable,
+                }
+            }).collect::<Vec<_>>()
+        }
+        CompileMode::Test => {
+            let mut base = targets.iter().filter(|t| {
+                t.tested()
+            }).map(|t| {
+                BuildProposal {
+                    target: t,
+                    profile: if t.is_example() {dep} else {profile},
+                    required: !required_features_filterable,
+                }
+            }).collect::<Vec<_>>();
+
+            // Always compile the library if we're testing everything as
+            // it'll be needed for doctests
+            if let Some(t) = targets.iter().find(|t| t.is_lib()) {
+                if t.doctested() {
+                    base.push(BuildProposal {
+                        target: t,
+                        profile: dep,
+                        required: !required_features_filterable,
+                    });
+                }
+            }
+            base
+        }
+        CompileMode::Build | CompileMode::Check => {
+            targets.iter().filter(|t| {
+                t.is_bin() || t.is_lib()
+            }).map(|t| BuildProposal {
+                target: t,
+                profile: profile,
+                required: !required_features_filterable,
+            }).collect()
+        }
+        CompileMode::Doc { .. } => {
+            targets.iter().filter(|t| {
+                t.documented()
+            }).map(|t| BuildProposal {
+                target: t,
+                profile: profile,
+                required: !required_features_filterable,
+            }).collect()
+        }
+        CompileMode::Doctest => {
+            if let Some(t) = targets.iter().find(|t| t.is_lib()) {
+                if t.doctested() {
+                    return vec![BuildProposal {
+                        target: t,
+                        profile: profile,
+                        required: !required_features_filterable,
+                    }];
+                }
+            }
+
+            Vec::new()
+        }
+    }
+}
+
+/// Given a filter rule and some context, propose a list of targets
+fn propose_indicated_targets<'a>(pkg: &'a Package,
+                                 rule: FilterRule,
+                                 desc: &'static str,
+                                 is_expected_kind: fn(&Target) -> bool,
+                                 profile: &'a Profile) -> CargoResult<Vec<BuildProposal<'a>>> {
+    match rule {
+        FilterRule::All => {
+            let result = pkg.targets().iter().filter(|t| is_expected_kind(t)).map(|t| {
+                BuildProposal {
+                    target: t,
+                    profile: profile,
+                    required: false,
+                }
+            });
+            return Ok(result.collect());
+        }
+        FilterRule::Just(names) => {
+            let mut targets = Vec::new();
+            for name in names {
+                let target = pkg.targets().iter().find(|t| {
+                    t.name() == *name && is_expected_kind(t)
+                });
+                let t = match target {
+                    Some(t) => t,
+                    None => {
+                        let suggestion = pkg.find_closest_target(name, is_expected_kind);
+                        match suggestion {
+                            Some(s) => {
+                                let suggested_name = s.name();
+                                bail!("no {} target named `{}`\n\nDid you mean `{}`?",
+                                      desc, name, suggested_name)
+                            }
+                            None => bail!("no {} target named `{}`", desc, name),
+                        }
+                    }
+                };
+                debug!("found {} `{}`", desc, name);
+                targets.push(BuildProposal {
+                    target: t,
+                    profile: profile,
+                    required: true,
+                });
+            }
+            return Ok(targets);
+        }
+    }
+}
+
+/// Collect the targets that are libraries or have all required features available.
+fn filter_compatible_targets<'a>(mut proposals: Vec<BuildProposal<'a>>,
+                                 features: &HashSet<String>)
+        -> CargoResult<Vec<(&'a Target, &'a Profile)>> {
+    let mut compatible = Vec::with_capacity(proposals.len());
+    for proposal in proposals.drain(..) {
+        let unavailable_features = match proposal.target.required_features() {
+            Some(rf) => rf.iter().filter(|f| !features.contains(*f)).collect(),
+            None => Vec::new(),
+        };
+        if proposal.target.is_lib() || unavailable_features.is_empty() {
+            compatible.push((proposal.target, proposal.profile));
+        } else if proposal.required {
+            let required_features = proposal.target.required_features().unwrap();
+            let quoted_required_features: Vec<String> = required_features.iter()
+                                                                         .map(|s| format!("`{}`",s))
+                                                                         .collect();
+            bail!("target `{}` requires the features: {}\n\
+                  Consider enabling them by passing e.g. `--features=\"{}\"`",
+                  proposal.target.name(),
+                  quoted_required_features.join(", "),
+                  required_features.join(" "));
+        }
+    }
+    Ok(compatible)
+}
+
 /// Given the configuration for a build, this function will generate all
 /// target/profile combinations needed to be built.
 fn generate_targets<'a>(pkg: &'a Package,
@@ -358,133 +558,44 @@ fn generate_targets<'a>(pkg: &'a Package,
         CompileMode::Doc { .. } => &profiles.doc,
         CompileMode::Doctest => &profiles.doctest,
     };
-    let mut targets = match *filter {
-        CompileFilter::Everything { .. } => {
-            match mode {
-                CompileMode::Bench => {
-                    pkg.targets().iter().filter(|t| t.benched()).map(|t| {
-                        (t, profile)
-                    }).collect::<Vec<_>>()
-                }
-                CompileMode::Test => {
-                    let deps = if release {
-                        &profiles.bench_deps
-                    } else {
-                        &profiles.test_deps
-                    };
-                    let mut base = pkg.targets().iter().filter(|t| {
-                        t.tested()
-                    }).map(|t| {
-                        (t, if t.is_example() {deps} else {profile})
-                    }).collect::<Vec<_>>();
-
-                    // Always compile the library if we're testing everything as
-                    // it'll be needed for doctests
-                    if let Some(t) = pkg.targets().iter().find(|t| t.is_lib()) {
-                        if t.doctested() {
-                            base.push((t, deps));
-                        }
-                    }
-                    base
-                }
-                CompileMode::Build | CompileMode::Check => {
-                    pkg.targets().iter().filter(|t| {
-                        t.is_bin() || t.is_lib()
-                    }).map(|t| (t, profile)).collect()
-                }
-                CompileMode::Doc { .. } => {
-                    pkg.targets().iter().filter(|t| t.documented())
-                       .map(|t| (t, profile)).collect()
-                }
-                CompileMode::Doctest => {
-                    if let Some(t) = pkg.targets().iter().find(|t| t.is_lib()) {
-                        if t.doctested() {
-                            return Ok(vec![(t, profile)])
-                        }
-                    }
 
-                    Vec::new()
-                }
-            }
+    let targets = match *filter {
+        CompileFilter::Everything { required_features_filterable } => {
+            let deps = if release {
+                &profiles.bench_deps
+            } else {
+                &profiles.test_deps
+            };
+            generate_auto_targets(mode, pkg.targets(), profile, deps, required_features_filterable)
         }
         CompileFilter::Only { lib, bins, examples, tests, benches } => {
             let mut targets = Vec::new();
 
             if lib {
                 if let Some(t) = pkg.targets().iter().find(|t| t.is_lib()) {
-                    targets.push((t, profile));
+                    targets.push(BuildProposal {
+                        target: t,
+                        profile: profile,
+                        required: true,
+                    });
                 } else {
                     bail!("no library targets found")
                 }
             }
 
-            {
-                let mut find = |names: &[String],
-                                desc,
-                                is_expected_kind: fn(&Target) -> bool,
-                                profile| {
-                    for name in names {
-                        let target = pkg.targets().iter().find(|t| {
-                            t.name() == *name && is_expected_kind(t)
-                        });
-                        let t = match target {
-                            Some(t) => t,
-                            None => {
-                                let suggestion = pkg.find_closest_target(name, is_expected_kind);
-                                match suggestion {
-                                    Some(s) => {
-                                        let suggested_name = s.name();
-                                        bail!("no {} target named `{}`\n\nDid you mean `{}`?",
-                                              desc, name, suggested_name)
-                                    }
-                                    None => bail!("no {} target named `{}`", desc, name),
-                                }
-                            }
-                        };
-                        debug!("found {} `{}`", desc, name);
-
-                        targets.push((t, profile));
-                    }
-                    Ok(())
-                };
-                find(bins, "bin", Target::is_bin, profile)?;
-                find(examples, "example", Target::is_example, build)?;
-                find(tests, "test", Target::is_test, test)?;
-                find(benches, "bench", Target::is_bench, &profiles.bench)?;
-            }
+            targets.append(&mut propose_indicated_targets(
+                pkg, bins, "bin", Target::is_bin, profile)?);
+            targets.append(&mut propose_indicated_targets(
+                pkg, examples, "example", Target::is_example, build)?);
+            targets.append(&mut propose_indicated_targets(
+                pkg, tests, "test", Target::is_test, test)?);
+            targets.append(&mut propose_indicated_targets(
+                pkg, benches, "bench", Target::is_bench, &profiles.bench)?);
             targets
         }
     };
 
-    //Collect the targets that are libraries or have all required features available.
-    let mut compatible_targets = Vec::with_capacity(targets.len());
-    for (target, profile) in targets.drain(0..) {
-        if target.is_lib() || match target.required_features() {
-            Some(f) => f.iter().all(|f| features.contains(f)),
-            None => true,
-        } {
-            compatible_targets.push((target, profile));
-            continue;
-        }
-
-        if match *filter {
-            CompileFilter::Everything { required_features_filterable } =>
-                !required_features_filterable,
-            CompileFilter::Only { .. } => true,
-        } {
-            let required_features = target.required_features().unwrap();
-            let quoted_required_features: Vec<String> = required_features.iter()
-                                                                         .map(|s| format!("`{}`",s))
-                                                                         .collect();
-            bail!("target `{}` requires the features: {}\n\
-                  Consider enabling them by passing e.g. `--features=\"{}\"`",
-                  target.name(),
-                  quoted_required_features.join(", "),
-                  required_features.join(" "));
-        }
-    }
-
-    Ok(compatible_targets)
+    filter_compatible_targets(targets, features)
 }
 
 /// Parse all config files to learn about build configuration. Currently
index 1c6c2e3a4fd282e0d7f186f8c5cb652a41939c3e..a802bffbae01de84d4d8dbee445753cdee67482d 100644 (file)
@@ -388,7 +388,8 @@ fn find_duplicates(dst: &Path,
                    pkg: &Package,
                    filter: &ops::CompileFilter,
                    prev: &CrateListingV1) -> BTreeMap<String, Option<PackageId>> {
-    let check = |name| {
+    let check = |name: String| {
+        // Need to provide type, works around Rust Issue #93349
         let name = format!("{}{}", name, env::consts::EXE_SUFFIX);
         if fs::metadata(dst.join(&name)).is_err() {
             None
@@ -402,13 +403,24 @@ fn find_duplicates(dst: &Path,
         CompileFilter::Everything { .. } => {
             pkg.targets().iter()
                          .filter(|t| t.is_bin())
-                         .filter_map(|t| check(t.name()))
+                         .filter_map(|t| check(t.name().to_string()))
                          .collect()
         }
         CompileFilter::Only { bins, examples, .. } => {
-            bins.iter().chain(examples)
-                       .filter_map(|t| check(t))
-                       .collect()
+            let all_bins: Vec<String> = bins.try_collect().unwrap_or_else(|| {
+                pkg.targets().iter().filter(|t| t.is_bin())
+                                    .map(|t| t.name().to_string())
+                                    .collect()
+            });
+            let all_examples: Vec<String> = examples.try_collect().unwrap_or_else(|| {
+                pkg.targets().iter().filter(|t| t.is_bin_example())
+                                    .map(|t| t.name().to_string())
+                                    .collect()
+            });
+
+            all_bins.iter().chain(all_examples.iter())
+                           .filter_map(|t| check(t.clone()))
+                           .collect::<BTreeMap<String, Option<PackageId>>>()
         }
     }
 }